Zhihao's Studio.

青芒 for Mac客户端开发笔记

Word count: 3,362 / Reading time: 12 min
2017/09/26 Share

关于轻芒和青芒

轻芒阅读是我每天都会打开的app,在它提供的两百多个channel中,我订阅了其中十几个channel,非常喜欢这个产品。这个产品的创始人是王俊煜,他曾经创办了一个更有名的公司——豌豆荚。关于轻芒和俊煜的更多介绍,请参照: 前沿科技的报道。虽然报道里强调了轻芒不是青芒,但是参照知了和知乎的关系,我还是决定把我的客户端取名为青芒。而且取名青芒的一大好处是logo好设计。做完原型的第二天,我就找了非著名设计师Joseph帮我设计了一枚logo,个人觉得还是很赞的。

非著名设计师Joseph设计的logo

非著名设计师Joseph设计的logo

为什么要做青芒

8月18日,我在github闲逛的时候发现了轻芒团队仍处于内测阶段的API文档,抱着试一试的态度,我给轻芒团队发送了内测申请的邮件,两天后得到了对方产品经理振辉的回复,同意了我参加内测的申请,在这表示感谢。收到邮件之后我兴奋异常,虽然之前也做了像微博、twitter、Instagram的客户端,但是这些产品早已有比我做的更好的产品上架到Apple Store了,估计除了我以外,没有人会用我开发的简陋版客户端。而轻芒则不同,目前还真没有mac和windows的客户端。

轻芒的用户通常对精神生活有丰富的追求,在macOS和windows中,我估计macOS用户会非常多,因此我选择了macOS平台,我认为这对我是一个机会,可能会有不少人试用这款软件。毕竟看到别人电脑或手机上运行着你开发的软件,还是会给人带来很大的成就感的。(偷笑)

据不完全统计,自从开源以来,已经有100多人下载了该软件。

青芒是怎么做的

知了是知乎日报的mac客户端,青芒跟知了有着相同的需求,即展示channel list、article list、article content三个部分,于是萌生了借鉴知了的念头,搜了一圈之后发现github上并没有类似的mac软件开源出来,于是做了决定:自己从头开始写,并且写完的第一件事就是将项目开源。我相信三段式布局的应用会适用很多场景,并且会给初学者们带来一定的启发。项目开源在我的github上,欢迎fork和star,特别感谢github开发者@jydemo、@immress对本项目提出的改进与code实现,效果图如下。

客户端视图(模仿的知了)

客户端视图(模仿的知了)

Notification Center视图(略丑)

Notification Center视图(略丑)

TouchBar视图

TouchBar视图

轻芒的API遵循RPC风格,形如 域名/主体.操作,主体和操作都使用小写开头的驼峰命名法,总体上还是比较容易调用的。青芒目前用到了其中的category.list、category.get、article.list、article.get。经测试,发现内测版里提供的channel还非常有限,只有11个,所以目前选择将所有channel展示出来。

开发青芒过程中,使用到了如下技术:HTTP请求、json解析、NSSplitView、WebKit、NSTableView、多线程(GCD)、imageView、自定义NSTextField、NSScrollView、自定义Window、NSTouchBar、NCWidgetProviding等技术,下面我来详细介绍一下青芒实现的过程。

界面部分

macOS/ios界面的构建一直是值得争论的话题,大体上有三种可选方式:

  1. 纯代码手写
  2. Xib文件
  3. Stroyboard

纯手写代码

纯手写代码是极客的不二选择,对于多人协作工作是很好的选择,但是缺点主要是不能直观的看到效果、编码速度很慢,例如初始化一个自定义的Button可能就需要二十行代码,非常不利于阅读。

Xib文件

Xib解决了上面的两个问题,提升了开发效率。其实Xib就是XML格式的文件,在编译过程中,被编译成Nib文件,每个Nib文件跟对应的ViewController关联。Xib的缺点是:代码可能回覆盖UI的设计,而且每个视图都需要单个的Xib,视图间的跳转依然需要代码控制。

Stroyboard

为了解决了Xib的问题,Apple提供了故事板功能。StoryBoard可以看成将很多Xib集中到了一起,像讲述一个故事一样,清晰的看到每个ViewController之间的跳转关系,跳转可以不用写代码了。因此我选用了StoryBoard来构建青芒。

青芒的StoryBoard文件截图

青芒的StoryBoard文件截图

从StroyBoard截图可以清晰的看到整个界面的布局,整个界面的布局是模仿的知乎日报Mac客户端(知了)做的,经典的三段式布局,NSSplitView可以将界面分成左右(或上下)的两部分。考虑到主题一列比较窄,而且主题和相应主题下的文章有强关联关系,因此借用SplitView将Overview Controller一分为二,这样三段式布局就算是完成了。

先从左边的OverView视图说起,这个视图里由两部分组成,第一个部分是一系列的主题按钮,点击之后,在右边的NSTableview里展示对应主题下的最新文章list。最新文章list点击之后,在右边的DetailView中用一个WebView展现文章的详情。

黑色的部分是TouchBar的视图,TouchBar是苹果在MacBook Pro 2016机型上加入的一个新的交互设备,围绕他的讨论有很多,我们有空可以单独聊一下,但是为了尊重这条价值4K的bar,我还是决定做了相应的适配,将主题通过按钮的形式放到了Bar上,Bar上的按钮和OverView的按钮是需要做联动功能的,在下文中会提到。

耐心地拖入相关控件、控制控件之间的相对位置,最初始的原型就算是完成了。当然,经过试用,还是可以发现优化的部分,比如Overview里的tableview并不是顶到window title的,这样用户拖拽那部分的时候,窗口依然可以移动,同样,WebView上方也是需要留白的。虽然只是个很小的细节,但真的很讨好用户。

自定义取代系统默认

界面完成后,就需要做功能了,但是等等,好像看起来不大对劲,为什么效果相比于知了差很多呢?下面的截图展现了青芒的第二版,相比于第一版,已经是把window的标题去掉了,但还是给人一种寨寨的、不够简洁的感觉。TableView中选中的颜色跟整体界面很不符。

第二版的青芒原型

第二版的青芒原型

为什么会出现这样的情况呢?因为一直到现在,我们都是采用的系统默认选项,没有设计人员的审美在里面。如何给用户带来私人订制的感觉呢?这就需要我们覆盖系统的默认行为和属性,具体来说,就是自定义子类,继承并覆盖父类中不符合开发者预期的部分。

从最后的结果来看,我们需要整个软件看起来背景是白色的,因此我们在每一个view加载的函数中,指定背景色为白色,使用

1
2
view.wantsLayer =true
self.view.layer?.backgroundColor=NSColor.white.cgColor

虽然所有视图背景色全部设为了白色,但是关闭、最大化、最小化按钮依然title上,而不是overview那部分,设置title为影藏,它们又不见了,让他们正确显示在overview中的做法是在window加载函数中加入:

1
2
3
self.window?.titleVisibility = .hidden
self.window?.titlebarAppearsTransparent =true
self.window?.styleMask.insert(.fullSizeContentView)

关于TableView中选中状态的背景色,可行的方法有两种。第一种是自定义Cell覆盖NSTableCellView,覆盖父类中的override var backgroundStyle:NSBackgroundStyle{}属性。第二种方法是github上的用户@jydemofork我的项目之后给我提的issue,自定义NSTableRowView,覆盖父类方法:

1
2
3
4
5
6
7
8
9
override func drawSelection(in dirtyRect:NSRect) {
super.drawSelection(in: dirtyRect)
var slectorRect =NSInsetRect(self.bounds,0,0)
NSColor(calibratedWhite:0.92, alpha:1.0).setStroke()
NSColor(calibratedWhite:0.92, alpha:1.0).setFill()
var slectorPath =NSBezierPath(roundedRect: slectorRect, xRadius:0, yRadius:0)
slectorPath.fill()
slectorPath.stroke()
}

然后实现tableview的代理方法,

1
2
3
4
func tableView(_tableView:NSTableView, rowViewForRow row:Int) ->NSTableRowView? {
let rowview = MyTableRowView(frame: .zero)
return rowview
}

两个主视图之间的分隔条比较粗,总让人觉得不美,解决方法还是自定义。覆盖NSSplitView,覆盖属性

1
2
3
override vardividerThickness:CGFloat{
get {return0.5}
}

如果用户愿意分隔条还是可以左右动的,想禁掉左右动的功能,实现NSSplitView的一个代理方法:

1
2
3
override func splitView(_ofDividerAtsplitView:NSSplitView, effectiveRect proposedEffectiveRect:NSRect, forDrawnRect drawnRect:NSRect, ofDividerAt dividerIndex:Int) ->NSRect{
return NSRect.zero
}

经过这一章节,不难发现还是代码靠谱(😎)。经过上面的调整,界面看起来简洁、清爽了不少,可以以假乱真了。

希望这一章节让大家明白,想要做出看起来美的东西,一定要去大胆地替代系统的默认选项。而具体的做法通常是覆盖父类中的属性和方法,记得要将组件和自定义的类关联起来。

美化后的界面

美化后的界面

一个坑

自定义TableView的Cell过程中,由于文章的标题通常是比较长的,因此用NSTextField无法放下,必须使用NSTextView,而NSTextView默认是可以上下左右滑动的,所以在文章列表中上下滑动的时候,每当滑动到TextView里,滑动事件就会白TextView捕获,TableView中的Scroll view没有机会捕获了。

解决的方法和上一节一样,通过覆盖cell里(请注意是Cell,不是tableview)的scroll view,重写hitTest方法

1
2
3
override func hitTest(_point:NSPoint) ->NSView? {
return nil
}

告诉cell,这个滑动事件我不处理了,请交给别人处理吧。

NSTouchBar

为了赶时髦,应用内做了TouchBar和通知中心的内容。TouchBar需要注意的是NSWindow和NSViewController之间的联动。从NSWindow到NSViewController:

1
let myViewcontroller =self.window?.contentViewControlleras!mainViewController

相反的过程:

1
let mywindowController=NSApplication.shared().windows[0].windowControlleras?windowController

这样就可以做到在TouchBar中按了某个按钮,在主界面里也可以看到按钮被选中的效果,满足了一致性。

Notification Center视图

通知中心做的蛮丑的,真的是为了尝试一下TodayExtension的功能而已。模仿知了,目前功能只是展示了首页的文章列表,点击文章可以用系统默认浏览器打开原文。其实这也就够了,毕竟通知中心就是为了看个大概用的,谁也不会经常点开看。通知中心需要注意的是要自定义视图的高度,通过
self.preferredContentSize=CGSize(width:self.view.frame.size.width, height:xxx)
完成。
最后要注意的是用URLSession请求数据,可以防止UI卡顿,UI卡顿给用户带来的感觉非常糟糕。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func getData(with urlString:String,success:@escaping(Data?)->Void, failure: ((Error)->Void)? =nil) {
guard let url =URL(string: urlString)else{
return
}
let task =URLSession.shared.dataTask(with: url) { (data:Data?, response:URLResponse?, error:Error?)in
DispatchQueue.main.async{
if let error = error {
failure?(error)
}else{
success(data)
}
}
}
task.resume()
}

文章具体内容视图

青芒团队提供的API里有web-content这一项,只要通过webview包装然后load一下就会将文字和图片展示出来,但是还是有一些细节需要考虑到的。比如有些图片是很大的,虽然限制了窗口不得小于一个值,但有些图片还是远超了这个大小。web-content是没有标题的,看起来会比较突兀,标题和标题需要你手动加上。图片过大时,窗口是可以左右滑动的,怎么把他限制不能滑动。字体如何跟其他部分视图的字体做到没有违和感,都是需要考虑的问题,这些用前端里的CSS样式可以控制,需要开发者有一定的前端开经验。由于本公众号重心在果教,因此这里就不具体展开叙述了,感兴趣的朋友可以参考我的github

青芒的宣传

做完青芒之后,考虑到整个项目是用最新的swift4写成,于是我在微博上@了swiftLanguage,博主是一个不大不小的V,关注者多是对swift感兴趣的或从业人员。短短两天,该微博获得了1万多次阅读,最后我欣喜的发现,王俊煜也给该微博点了赞,感谢俊昱的鼓励与肯定。

微博截图

微博截图

结束语

青芒的开发只用了两天,其实可以做的东西还有很多。试用软件之后如果您对软件有任何的意见与建议,欢迎留言。

如果您喜欢这款软件,也欢迎您将它推荐给您的朋友们。让我们一起将青芒变得更好!

我会在订阅号里不定期分享我个人的macOS/ios开发心得和开发笔记,也会在里面发表对于苹果产品/框架/趋势的拙见,希望爱好科技产品或者苹果生态圈的开发者关注。相信本公众号一定能给您带来收获和启发。
【欢迎扫码关注微信公众号】

CATALOG
  1. 1. 关于轻芒和青芒
  2. 2. 为什么要做青芒
  3. 3. 青芒是怎么做的
  4. 4. 界面部分
    1. 4.1. 纯手写代码
    2. 4.2. Xib文件
    3. 4.3. Stroyboard
  5. 5. 自定义取代系统默认
  6. 6. 一个坑
  7. 7. NSTouchBar
  8. 8. Notification Center视图
  9. 9. 文章具体内容视图
  10. 10. 青芒的宣传
  11. 11. 结束语